package com.sjsu.cmpe239.connection;


import java.sql.*; 
import java.util.*; 

/** A class for preallocating, recycling, and managing 
 * JDBC connections. 
 * 
 */ 

public class ConnectionPool implements Runnable { 
	private String driver="com.mysql.jdbc.Driver", url="jdbc:mySql://localhost:3306/239db", username="root", password=""; 
	private int maxConnections=7, initialConnections=4; 
	private boolean waitIfBusy=true; 
	private Vector availableConnections, busyConnections; 
	private boolean connectionPending = false; 
	private static ConnectionPool singletonConnPoolObj;

	private ConnectionPool()  { 
		this.driver = driver; 
		this.url = url; 
		this.username = username; 
		this.password = password; 
		this.maxConnections = maxConnections; 
		this.waitIfBusy = waitIfBusy; 
		if (initialConnections > maxConnections) { 
			initialConnections = maxConnections; 
		} 
		availableConnections = new Vector(initialConnections); 
		busyConnections = new Vector(); 
		for(int i=0; i<initialConnections; i++) { 
			try {
				availableConnections.addElement(makeNewConnection( ));
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
		} 
	} 
	public Object clone()throws CloneNotSupportedException
	{
		throw new CloneNotSupportedException(); 
	}
	public static synchronized ConnectionPool getConnectionPoolInstance()
	{
		if (singletonConnPoolObj == null){
			singletonConnPoolObj = new ConnectionPool();
		}
		return singletonConnPoolObj;
	}

	public synchronized Connection getConnection() 
	throws SQLException { 
		if (!availableConnections.isEmpty()) { 
			Connection existingConnection=(Connection)availableConnections.lastElement(); 
			int lastIndex = availableConnections.size() - 1; 
			availableConnections.removeElementAt(lastIndex); 

			if (existingConnection.isClosed()) { 
				notifyAll(); // Freed up a spot for anybody waiting 
				return(getConnection()); 
			} else { 
				busyConnections.addElement(existingConnection); 

				return(existingConnection); 
			} 
		} else { 

			if ((totalConnections() < maxConnections) && 
					!connectionPending) { 
				makeBackgroundConnection(); 
			} else if (!waitIfBusy) { 
				throw new SQLException("Connection limit reached"); 
			} 
			// Wait for either a new connection to be established 
			// (if you called makeBackgroundConnection) or for 
			// an existing connection to be freed up. 
			try { 
				wait(); 
			} catch(InterruptedException ie) {} 
			// Someone freed up a connection, so try again. 
			System.out.println(availableConnections.size() +"connections are availble b4 leasing");
			return(getConnection()); 
		}

	} 

	// You can't just make a new connection in the foreground 
	// when none are available, since this can take several 
	// seconds with a slow network connection. Instead, 
	// start a thread that establishes a new connection, 
	// then wait. You get woken up either when the new connection 
	// is established or if someone finishes with an existing 
	// connection. 

	private void makeBackgroundConnection() { 
		connectionPending = true; 
		try {

			Thread connectThread = new Thread(this); 
			connectThread.start(); 
			System.out.println("started Making backgrnd conn in connectionPool");
		} catch(OutOfMemoryError oome) { 
			// Give up on new connection 
		} 
	} 

	public void run() { 
		try { 
			Connection connection = makeNewConnection(); 
			synchronized(this) { 
				availableConnections.addElement(connection); 
				connectionPending = false; 
				notifyAll(); 
			} 
		} catch(Exception e) { // SQLException or OutOfMemory 
			// Give up on new connection and wait for existing one 
			// to free up. 
		} 
	} 

	// This explicitly makes a new connection. Called in 
	// the foreground when initializing the ConnectionPool, 
	// and called in the background when running. 

	private Connection makeNewConnection() 
	throws SQLException { 
		try { 
			// Load database driver if not already loaded 
			Class.forName(driver); 
			// Establish network connection to database 
			Connection connection= DriverManager.getConnection(url, username, password); 
			return(connection); 
		} catch(ClassNotFoundException cnfe) { 
			// Simplify try/catch blocks of people using this by 
			// throwing only one exception type. 
			throw new SQLException("Can't find class for driver: " + 
					driver); 
		} 
	} 

	public synchronized void free(Connection connection) { 
		busyConnections.removeElement(connection); 
		availableConnections.addElement(connection); 
		// Wake up threads that are waiting for a connection 
		notifyAll(); 
	} 

	public synchronized int totalConnections() { 
		return(availableConnections.size() + 
				busyConnections.size()); 
	} 
	public synchronized int availableConnections() { 
		return(availableConnections.size() );

	} 
	public synchronized int busyConnections() { 
		return(busyConnections.size());

	} 



	public synchronized String toString() { 
		String info= "ConnectionPool(" + url + "," + username + ")" + 
		", available=" + availableConnections.size() + 
		", busy=" + busyConnections.size() + 
		", max=" + maxConnections; 
		return(info); 
	} 
}
